﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Configuration;
using System.IO;
using System.Data;
using Sync3DCartDatabase.cartAPI;
using System.Xml.Linq;

namespace Sync3DCartDatabase
{
    public partial class SyncDatabase : System.Web.UI.Page
    {

        private ServiceConfig _config = null;

        protected void Page_Load(object sender, EventArgs e)
        {
            _config = (ServiceConfig)ConfigurationManager.GetSection("SyncServiceConfigSection/ServiceConfig");
            SyncDatabaseFromFile();
        }

        #region Private Methods
        /// <summary>
        /// This method will sync flat file to ThreeDCart
        /// </summary>
        private void SyncDatabaseFromFile()
        {
            string sourceFileName = string.Empty;
            string fileText = string.Empty;
            try
            {                
                //Fetch the latest file names to work
                FetchFileNames(ref sourceFileName);
                LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("File Names fetched. SourceFile: ", sourceFileName));                
                using (MemoryStream fileInMemory = new MemoryStream())
                {
                    CFTPHelper.Download(_config.FTPConnectionInfo, fileInMemory, sourceFileName);
                    fileInMemory.Position = 0;
                    StreamReader sw = new StreamReader(fileInMemory);
                    fileText = sw.ReadToEnd();
                }
                LogMessage(MessageWriter.ISEnumLogLevel.INFO, "File downloaded");
                if (string.IsNullOrWhiteSpace(fileText))
                    throw new FileNotFoundException("No Data was found to map");
                MapAndUpdateData(fileText);
                Response.Write("Sync Completed");
            }
            catch (Exception ex)
            {
                LogMessage(MessageWriter.ISEnumLogLevel.ERROR, ex.ToString());
                using (StreamWriter writer = new StreamWriter(string.Concat(AppDomain.CurrentDomain.BaseDirectory, Path.DirectorySeparatorChar, "ErrorFile.txt")))
                {
                    writer.Write(fileText);
                }
                Response.Write("Sync Completed with some errors");
            }
        }

        private void UpdatePreviousFiles()
        {
            try
            {
                string[] paths = Directory.GetFiles(_config.WorkingFolderPath);
                if (paths.Length > 0)
                {
                    LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("Files found in working folder: ", paths.Length));
                    foreach (string filePath in paths)
                    {
                        try
                        {
                            LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("Mapping file in working folder : ", filePath));
                            MapAndUpdateData(filePath);
                        }
                        catch (Exception ex)
                        {
                            LogMessage(MessageWriter.ISEnumLogLevel.ERROR, ex.ToString());
                            if (File.Exists(filePath))
                            {
                                //Move file to Error Folder
                                File.Move(filePath, string.Concat(_config.ErrorFolderPath, Path.GetFileName(filePath)));
                                LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("File moved to Error folder. ", filePath));
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogMessage(MessageWriter.ISEnumLogLevel.ERROR, ex.ToString());
            }
        }

        /// <summary>
        /// This method will map the data from flat file to given mapping
        /// and update via 3D cart API.
        /// </summary>
        /// <param name="destinationFileName"></param>
        private void MapAndUpdateData(string fileText)
        {
            // Map the current information according to mapping provided
            DataTable mappedInfo = MapFile(fileText);
            LogMessage(MessageWriter.ISEnumLogLevel.INFO, "File Mapped");
            //update mapped information into 3Dcart inventory
            UpdateSource(mappedInfo);
            //Log for successful completion
            LogMessage(MessageWriter.ISEnumLogLevel.INFO, "File successfully updated.");
        }

        /// <summary>
        /// This method will return the file name to be downloaded
        /// and the path of downloaded file
        /// </summary>
        /// <param name="sourceFileName"></param>
        /// <param name="destinationFileName"></param>
        private void FetchFileNames(ref string sourceFileName)
        {
            sourceFileName = string.Concat(_config.SourceFolderPath, "/", DateTime.Now.ToString("yyyyMMdd"), "P.txt");            
        }

        /// <summary>
        /// Update all the row into 3 D cart database
        /// </summary>
        /// <param name="mappedInfo"></param>
        private void UpdateSource(DataTable mappedInfo)
        {
            cartAPISoapClient client = new cartAPISoapClient();
            int errorCount = 0;
            try
            {
                client.Open();
                foreach (DataRow row in mappedInfo.Rows)
                {
                    try
                    {
                        XElement response = client.updateProductInventory(_config.cartConnectionInfo.StoreUrl, _config.cartConnectionInfo.UserKey,
                            row["id"].ToString(), Convert.ToInt32(row["Stock"]), true, "");

                        LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("Response recieved: ", response.ToString()));

                        if (response.ToString().ToLower().Contains("Error".ToLower()))
                        {
                            errorCount++;
                            LogMessage(MessageWriter.ISEnumLogLevel.ERROR, string.Concat("Error recieved for id: ", row["id"].ToString()));
                        }
                    }
                    catch (Exception ex)
                    {
                        LogMessage(MessageWriter.ISEnumLogLevel.ERROR, ex.ToString());
                    }
                }
                if (errorCount > 0)
                {
                    throw new ApplicationException("One or more products could not be updates succesfully");
                }
            }
            finally
            {
                client.Close();
            }
        }

        /// <summary>
        /// This method will map the current information in flat file 
        /// acording to given mappings
        /// </summary>
        /// <param name="workingFilePath">
        /// path of file, to be worked upon
        /// </param>
        /// <returns></returns>
        private DataTable MapFile(string fileText)
        {
            DataTable mappedDataInfo = new DataTable();
            MemoryStream fileStream = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(fileText));
            //Read all the lines in file
            using (StreamReader reader = new StreamReader(fileStream))
            {
                //read first line for headers
                string line = reader.ReadLine();
                string[] valuesArray = line.Split(new string[] { "\t" }, StringSplitOptions.None);

                //Add column names to dataTable
                foreach (string columnName in valuesArray)
                {
                    //If the couln has no name insert empty column
                    if (string.IsNullOrEmpty(columnName))
                        mappedDataInfo.Columns.Add("");
                    else
                    {
                        //If column has name check for mapped name
                        string colName = columnName;
                        MappedColumnInfo info = _config.MappedColumnsDetail.MappedColumnsList.Find(
                            delegate(MappedColumnInfo columnInfo)
                            {
                                return string.Equals(columnInfo.ColumnName, columnName, StringComparison.InvariantCultureIgnoreCase);
                            }
                        );
                        //if mapped name found add mapped column Name
                        if (info != null)
                            colName = info.MappedColumnName;
                        if (mappedDataInfo.Columns.Contains(colName))
                            mappedDataInfo.Columns.Add(colName + " ");
                        //if no mapping exist add column name as it is
                        else
                            mappedDataInfo.Columns.Add(colName);
                    }
                }
                LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("Columns Added ", mappedDataInfo.Columns.Count));

                //Insert all dataValues in tables
                while (reader.EndOfStream == false)
                {
                    line = reader.ReadLine();
                    DataRow dr = mappedDataInfo.NewRow();
                    dr.ItemArray = line.Split(new string[] { "\t" }, StringSplitOptions.None);
                    MapValues(ref dr);
                    mappedDataInfo.Rows.Add(dr);
                }

                LogMessage(MessageWriter.ISEnumLogLevel.INFO, string.Concat("Rows Added ", mappedDataInfo.Rows.Count));
            }
            return mappedDataInfo;
        }

        /// <summary>
        /// This method will convert all the data values according to given mapping
        /// </summary>
        /// <param name="inputRow"></param>
        private void MapValues(ref DataRow inputRow)
        {
            foreach (MappedColumnInfo valueMapping in _config.MappedColumnsDetail.MappedValuesColumnsList)
            {
                string tempValue = inputRow[valueMapping.MappedColumnName].ToString();
                MappedColumnValueInfo value = valueMapping.MappedValuesList.Find(delegate(MappedColumnValueInfo info)
                {
                    return (info.OriginalValue == tempValue);
                });
                inputRow[valueMapping.MappedColumnName] = value.MappedValue;
            }
        }

        #endregion


        #region Logging
        private void LogMessage(MessageWriter.ISEnumLogLevel logLevel, string message)
        {
            MessageWriter.EventMessage(message, logLevel, this.GetType().FullName);
        }
        #endregion
    }
}

